#include "GLRenderer.h"
#include "GLTexture.h"
#include "GLPrimitiveRenderer.h"
#include <gl\glew.h>

bool _supportVA = false, _supportVBO = false, _supportShaders = false;
GLuint _fragShader, _vertShader, _program;
GLuint _attribVertexPosition, _attribVertexColor, _attribVertexTexCoord;
GLuint _uniformMVP, _uniformBoundTexture;

char *LoadFileAsString(const char *file)
{
	wxFileInputStream stream(file);

	if (!stream.IsOk())
		return nullptr;

	auto len = stream.GetLength();
	char *buf = new char[len + 1];
	stream.Read(buf, len);
	buf[len] = 0;

	return buf;
}

GLuint LoadShader(const char *source, const GLenum &type)
{
	auto shader = glCreateShader(type);
	glShaderSource(shader, 1, &source, NULL);
	glCompileShader(shader);

	GLint status;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

	if (status == GL_FALSE)
	{
		GLint len;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);

		char *infoLog = new char[len];
		GLint actualLen;
		glGetShaderInfoLog(shader, len, &actualLen, infoLog);
		delete infoLog;

		glDeleteShader(shader);
		return 0;
	}

	return shader;
}

GLuint LoadProgram(const GLuint &vert, const GLuint &frag)
{
	auto program = glCreateProgram();
	
	glAttachShader(program, vert);
	glAttachShader(program, frag);

	glLinkProgram(program);

	GLint status;
	glGetProgramiv(program, GL_LINK_STATUS, &status);

	if (status == GL_FALSE)
	{
		GLint len;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);

		char *infoLog = new char[len];
		GLint actualLen;
		glGetProgramInfoLog(program, len, &actualLen, infoLog);
		delete infoLog;

		glDeleteProgram(program);
		return 0;
	}

	glUseProgram(program);
	return program;
}

int CheckError() { return glGetError(); }

void InitExtensions()
{
	static bool _inited = false;
	glewInit();

	if (!_inited)
	{
		_supportVA = _supportVBO = false;

		if (glewIsSupported("GL_EXT_vertex_array"))
			_supportVA = true;

		if (glewIsSupported("GL_ARB_vertex_buffer_object"))
			_supportVBO = true;

		if (glewIsSupported("GL_ARB_vertex_shader") &&
			glewIsSupported("GL_ARB_fragment_shader") &&
			glewIsSupported("GL_ARB_shading_language_100"))
		{
			_supportShaders = true;
					
			auto vertSource = LoadFileAsString("glsl\\main.vs");
			auto fragSource = LoadFileAsString("glsl\\main.fs");
					
			_vertShader = LoadShader(vertSource, GL_VERTEX_SHADER);
			_fragShader = LoadShader(fragSource, GL_FRAGMENT_SHADER);

			delete vertSource;
			delete fragSource;

			if (_vertShader == 0 ||
				_fragShader == 0)
				_supportShaders = false;
			else
			{
				_program = LoadProgram(_vertShader, _fragShader);
					
				if (_program == 0)
					_supportShaders = false;
				else
				{
					_attribVertexPosition = glGetAttribLocation(_program, "vertexPosition");
					_attribVertexColor = glGetAttribLocation(_program, "vertexColor");
					_attribVertexTexCoord = glGetAttribLocation(_program, "vertexTexCoord");
					
					_uniformMVP = glGetUniformLocation(_program, "modelViewProjection");
					_uniformBoundTexture = glGetUniformLocation(_program, "boundTexture");
				}
			}
		}

		_inited = true;
	}
}

void GLRenderer::Translate(const float &x, const float &y, const float &z)
{
	_modelView.translate(Point3f(x, y, z));
}

void GLRenderer::Scale(const float &x, const float &y, const float &z)
{
	_modelView.scale(Point3f(x, y, z));
}

void GLRenderer::Rotate(const float &angle, const int &axisX, const int &axisY, const int &axisZ)
{
	_modelView.rotate(MathHelper::DegreesToRadians(angle), Point3i(axisX, axisY, axisZ));
}

void GLRenderer::PopState()
{
	_modelView = _states.top();
	_states.pop();
}

void GLRenderer::PushState()
{
	_states.push(_modelView);
}

void GLRenderer::SetupMatrices(const int &width, const int &height)
{
	glViewport(0, 0, width, height);
	_viewport = Point4i(0, 0, width, height);
	_3dProjection.perspective(MathHelper::DegreesToRadians(75), (float)width / (float)height, 0.1f, 99999);
	_2dProjection.ortho(0, width, height, 0, -1, 1);
}

void GLRenderer::Setup3DRender()
{
	_projection = &_3dProjection;
}

void GLRenderer::Setup2DRender()
{
	_projection = &_2dProjection;
}

void GLRenderer::PrepareRender()
{
	if (_supportShaders)
	{
		auto mvp = *_projection;
		mvp.multiply(_modelView);
		glUniformMatrix4fv(_uniformMVP, 1, GL_FALSE, &mvp.Values[0]);
		return;
	}

	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf((GLfloat*)_projection);

	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf((GLfloat*)&_modelView);
}

PrimitiveRendererBase *GLRenderer::CreatePrimitiveRenderer()
{
	return new GLPrimitiveRenderer(this);
}

ITexture *GLRenderer::CreateTexture()
{
	return new GLTexture();
}